package org.softee.extractor.sax;

import java.io.StringWriter;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.softee.extractor.QNameMatcher;
import org.softee.extractor.sax.helper.DomBuilder;
import org.softee.extractor.sax.helper.ResultHandler;
import org.softee.extractor.sax.helper.XmlExtractor;
import org.softee.extractor.sax.helper.XmlStringBuilder;
import org.w3c.dom.Document;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;

public class XmlExtractorFactory {
    private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();

    private XmlExtractorFactory() {
        // Intentionally non-instantiable
    }

    public static XmlExtractor<Document> createDomExtractor(QNameMatcher qNameMatcher, ResultHandler<Document> resultHandler)
            throws SAXNotRecognizedException, SAXNotSupportedException, ParserConfigurationException {
        return new XmlExtractor<Document>(qNameMatcher, resultHandler, new DomBuilder());
    }

    public static XmlExtractor<Document> createSerializedDomExtractor(QNameMatcher qNameMatcher, ResultHandler<String> resultHandler)
            throws SAXNotRecognizedException, SAXNotSupportedException, ParserConfigurationException {
        return new XmlExtractor<Document>(qNameMatcher, documentHandler(resultHandler), new DomBuilder());
    }

    public static XmlExtractor<String> createStringExtractor(QNameMatcher qNameMatcher, ResultHandler<String> resultHandler)
            throws SAXNotRecognizedException, SAXNotSupportedException, ParserConfigurationException {
        return new XmlExtractor<String>(qNameMatcher, resultHandler, new XmlStringBuilder());
    }

    /**
     * @param handler a handler for String events
     * @return a handler for Document events that will adapt/convert to String and notify handler
     */
    private static ResultHandler<Document> documentHandler(final ResultHandler<String> handler) {
        return new ResultHandler<Document>() {

            @Override
            public void accept(Document document) {
                Source source = new DOMSource(document);
                StringWriter sw = new StringWriter();
                Result result = new StreamResult(sw);
                try {
                    createTransformer().transform(source, result);
                    handler.accept(sw.toString());
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                }

            }
        };
    }

    private static Transformer createTransformer() throws TransformerConfigurationException, TransformerFactoryConfigurationError {
            Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
            // as we are using a character-based format, it makes no sense to include header
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            return transformer;
    }
}
